#include "vanila/baseobject.h"
#include "vanila/dsobject.h"
#include "vanila/object.h"
#include "vanila/value.h"
#include "vanila/compiler.h"
#include "vanila/scanner.h"
#include "vanila/token.h"
#include "vanila/parser.h"
#include "vanila/disassembler.h"
#include "vanila/virtualmachine.h"
#include "vanila/allocator.h"
#include <string>
#include <map>
#include <list>
#include <cstring>

namespace vanila
{
static VirtualMachine* vm = utils::Singleton<VirtualMachine>::instance();
static Allocator* allocator = utils::Singleton<Allocator>::instance();
static Parser* parser = utils::Singleton<Parser>::instance();
static Scanner* scanner = utils::Singleton<Scanner>::instance();

//! \brief init the Compiler!
void Compiler::init(const char* source) noexcept
{
    scanner->init(source);
    
    this->_previousToken = Token();
    this->_currentToken = Token();
    this->_currentEnvironment = nullptr;
    this->_currentClassEnvironment = nullptr;
    this->_hadError = false;
    this->_panicMode = false;
}

//! \brief comile the source code
ObjectFunction* Compiler::compile()
{
    Environment environment(FunctionType::SCRIPT);
    this->initEnvironment(&environment);

    this->advanceToken();
    
    while (!this->matchTokenType(TokenType::ENDOFFILE))
        this->compileDeclaration();

    this->consumeToken(TokenType::ENDOFFILE, "Expect end of expression.");

    ObjectFunction* function = this->endCompiler();
    return this->_hadError ? nullptr : function;
}

//! \brief compile a declaration
void Compiler::compileDeclaration()
{
    if (this->matchTokenType(TokenType::CLASS))
        this->compileClassDeclaration();
    else if (this->matchTokenType(TokenType::VAR))
        this->compileVarDeclaration();
    else if (this->matchTokenType(TokenType::FUN))
        this->compileFunDeclaration();
    else
        this->compileStatement();

    if (this->_panicMode)
        this->synchronize();
}

//! \brief comile a statement
void Compiler::compileStatement()
{
    if (this->matchTokenType(TokenType::IF))
        this->compileIfStatement();
    else if (this->matchTokenType(TokenType::RETURN))
        this->compileReturnStatement();
    else if (this->matchTokenType(TokenType::WHILE))
        this->compileWhileStatement();
    else if (this->matchTokenType(TokenType::FOR))
        this->compileForStatement();
    // meet a '{'
    else if (this->matchTokenType(TokenType::LEFT_BRACE))
    {
        this->beginScope();
        this->compileBlock();
        this->endScope();
    }
    else
        this->compileExpressionStatement();
}

//! \brief compile a expression statement
void Compiler::compileExpressionStatement()
{
    // an “expression statement” is simply an expression followed by a semicolon
    this->compileExpression();
    this->consumeToken(TokenType::SEMICOLON, "Expect ';' after expression.");

    // eavluate the experssion, and just pop it from stack top
    this->emitBytecode(OpCode::POP);
}

//! \brief compile a if statement
void Compiler::compileIfStatement()
{
    this->consumeToken(TokenType::LEFT_PAREN, "Expect '(' after 'if'.");
    // condition expression
    this->compileExpression();
    this->consumeToken(TokenType::RIGHT_PAREN, "Expect ')' after condition.");

    // save the current code index
    uint32_t thenJump = this->emitJump(OpCode::JUMP_IF_FALSE);
    this->emitBytecode(OpCode::POP);

    // compile the then statements
    this->compileStatement();

    uint32_t elseJump = this->emitJump(OpCode::JUMP);

    // fill the jump distance
    this->patchJump(thenJump);
    this->emitBytecode(OpCode::POP);

    if (this->matchTokenType(TokenType::ELSE))
        this->compileStatement();
    
    this->patchJump(elseJump);
}

//! \brief compile a return statement
void Compiler::compileReturnStatement()
{
    if (this->_currentEnvironment->functionType() == FunctionType::SCRIPT)
        this->errorAtPreviousToken("Can't return from top-level code.");
    
    // return NIL
    if (this->matchTokenType(TokenType::SEMICOLON))
        this->emitReturn();
    else
    {
        if (this->_currentEnvironment->functionType() == FunctionType::INITIALIZER)
            this->errorAtPreviousToken("Can't return a value from an initializer.");

        this->compileExpression();
        this->consumeToken(TokenType::SEMICOLON, "Expect ';' after return value.");
        this->emitBytecode(OpCode::RETURN);
    }
}

//! \brief compile a while loop
void Compiler::compileWhileStatement()
{
    // record the jump back address
    uint32_t loopStart = this->currentChunk()->size();

    this->consumeToken(TokenType::LEFT_PAREN, "Expect '(' after 'while'.");
    // condition expression
    this->compileExpression();
    this->consumeToken(TokenType::RIGHT_PAREN, "Expect ')' after condition.");

    uint32_t exitJump = emitJump(OpCode::JUMP_IF_FALSE);
    this->emitBytecode(OpCode::POP);
    this->compileStatement();

    // Jump to record address
    this->emitLoop(loopStart);

    this->patchJump(exitJump);
    this->emitBytecode(OpCode::POP);
}

//! \brief compile a class declaration
void Compiler::compileClassDeclaration()
{
    // after a 'class' keyword should be a className identifier!
    this->consumeToken(TokenType::IDENTIFIER, "Expect class name.");
    // record the class name Token
    Token className = this->_previousToken;
    uint8_t nameConstant = this->makeIdentifierConstant(this->_previousToken);

    // class name is a object!
    this->declareVariable();
    this->emitBytecodeByte(OpCode::CLASS, nameConstant);
    this->defineVariable(nameConstant);

    // add a class environment
    ClassEnvironment classEnvironment{this->_currentClassEnvironment};
    this->_currentClassEnvironment = &classEnvironment;

    // inheritance!
    if (this->matchTokenType(TokenType::LESS))
    {
        // a class name after '<'
        this->consumeToken(TokenType::IDENTIFIER, "Expect superclass name.");
        // load the base class name
        this->compileNamedVariable(this->_previousToken, false);
        if (Token::identifiersEqual(className, this->_previousToken))
            this->errorAtPreviousToken("A class can't inherit from itself.");

        this->beginScope();
        // add a new scope, see "super" as a local variable!
        // it can be captured by method's environment as a upvalue
        //! \note for example, if currentEnvirnment is script(global):
        //! \note begin scope, and put super class as a local variable
        //! \note it will be see as a upvalue, and captured by the class's function
        //! \note after this script out, it will be close as a closed upvalue
        //! \note and move to the heap, been used by the subclass's method
        this->_currentEnvironment->addLocal(Token("super"));
        // define the "super" variable
        this->defineVariable(0);

        // load the 'class' object back to stack
        this->compileNamedVariable(className, false);
        
        this->emitBytecode(OpCode::INHERIT);
        classEnvironment.hasSuperclass = true;
    }

    // load the class name to the stack
    this->compileNamedVariable(className, false);

    this->consumeToken(TokenType::LEFT_BRACE, "Expect '{' before class body.");
    
    // compile methods!
    while (!this->checkTokenType(TokenType::RIGHT_BRACE) && !this->checkTokenType(TokenType::ENDOFFILE))
    {
        if (this->checkTokenType(TokenType::FUN))
            this->compileMethod();
        else if (this->checkTokenType(TokenType::VAR))
            this->compileField();
        else
            this->errorAtCurrentToken("Expect 'var' or 'fun' in class body.");
    }

    this->consumeToken(TokenType::RIGHT_BRACE, "Expect '}' after class body.");

    // pop the class name in the stack
    this->emitBytecode(OpCode::POP);

    if (classEnvironment.hasSuperclass)
    {
        this->endScope();
    }
    this->_currentClassEnvironment = this->_currentClassEnvironment->enclosing;
}

//! \brief compile a for loop
void Compiler::compileForStatement()
{
    this->beginScope();
    this->consumeToken(TokenType::LEFT_PAREN, "Expect '(' after 'for'.");
    
    // initializer
    if (this->matchTokenType(TokenType::SEMICOLON))
        // no initializer
        {}
    else if (this->matchTokenType(TokenType::VAR))
        // a local var define
        this->compileVarDeclaration();
    else
        // a experssion statement
        this->compileExpressionStatement();

    // record the loop start positon(includes condition and increment!)
    uint32_t loopStart = this->currentChunk()->size();

    uint32_t exitJump = 0;
    bool hasCondition = false;
    // check wheather has a condition
    if (!this->matchTokenType(TokenType::SEMICOLON))
    {
        // for loop has a condition expression!
        hasCondition = true;
        this->compileExpression();
        this->consumeToken(TokenType::SEMICOLON, "Expect ';' after loop condition.");
    
        // check wheather condition is false 
        exitJump = this->emitJump(OpCode::JUMP_IF_FALSE);
        // pop condition
        this->emitBytecode(OpCode::POP);
    }

    // check wheather has a increment
    if (!this->matchTokenType(TokenType::RIGHT_PAREN))
    {
        // the increment will be excuted after body, so if the condition pass,
        // it will meet this JUMP, the JUMP will send the ip to the start of body 
        uint32_t bodyJump = this->emitJump(OpCode::JUMP);
        // record the increment start address
        uint32_t incrementStart = this->currentChunk()->size();

        // compile the increse experssion
        this->compileExpression();
        this->emitBytecode(OpCode::POP);
        this->consumeToken(TokenType::RIGHT_PAREN, "Expect ')' after for clauses.");

        // after executing the increase, we should jump back to the start of condition
        this->emitLoop(loopStart);
        // if there is a increment, after executing body, ip will jump to the start of increment
        loopStart = incrementStart;
        // current ip is the start of body, so fill it!
        this->patchJump(bodyJump);
    }

    this->compileStatement();
    // after excute body, jump back to start of loop!
    this->emitLoop(loopStart);

    if (hasCondition)
    {
        // if has a condition, fill it jump address
        this->patchJump(exitJump);
        this->emitBytecode(OpCode::POP);
    }

    this->endScope();
}

//! \brief compile a var declaration
void Compiler::compileVarDeclaration()
{
    // after a 'Var' should be a identifier
    uint8_t global = this->registerVariableName("Expect variable name.");

    // after the varibale name, it may have a '=' to assign it
    if (this->matchTokenType(TokenType::EQUAL))
        // if so, call expression to evaluate the value to assign
        this->compileExpression();
    else
        // assign a nil
        this->emitBytecode(OpCode::NIL);

    this->consumeToken(TokenType::SEMICOLON, "Expect ';' after variable declaration.");
    
    this->defineVariable(global);
}

//! \brief compile a fun declaration
void Compiler::compileFunDeclaration()
{
    // see function as a variable! 
    uint8_t global = this->registerVariableName("Expect function name.");
    this->_currentEnvironment->markInitialized();
    this->compileFunction(FunctionType::FUNCTION);
    this->defineVariable(global);
}

//! \brief compile a experssion
void Compiler::compileExpression()
{
    parser->parsePrecedence(this, Precedence::ASSIGNMENT);
}

//! \brief compile a block
void Compiler::compileBlock()
{
    while (!this->checkTokenType(TokenType::RIGHT_BRACE) && !this->checkTokenType(TokenType::ENDOFFILE))
        this->compileDeclaration();
    
    this->consumeToken(TokenType::RIGHT_BRACE, "Expect '}' after block.");
}

//! \brief compile a function object
void Compiler::compileFunction(FunctionType functionType)
{
    Environment environment(functionType);
    // 'initEnvironment' function will check the functionType
    // if it's a METHOD, it will add a "this" local variable
    this->initEnvironment(&environment);

    this->beginScope();

    this->consumeToken(TokenType::LEFT_PAREN, "Expect '(' after function name.");
    
    // function params
    if (!this->checkTokenType(TokenType::RIGHT_PAREN))
    {
        do 
        {
            this->currentFunction()->increaseArity();
            if (this->currentFunction()->arity() > 255)
                this->errorAtCurrentToken("Can't have more than 255 parameters.");
            
            // see param as the local variable!
            uint8_t constant = this->registerVariableName("Expect parameter name.");
            this->defineVariable(constant);

        } while (this->matchTokenType(TokenType::COMMA));
    }

    this->consumeToken(TokenType::RIGHT_PAREN, "Expect ')' after parameters.");
    this->consumeToken(TokenType::LEFT_BRACE, "Expect '{' before function body.");

    this->compileBlock();

    ObjectFunction* function = this->endCompiler();
    this->emitBytecodeByte(OpCode::CLOSURE, this->makeConstant(Value(function)));

    // attention! because the endCompiler, currentEnvironment is point to environmentnot
    // and the function object is that in environmentnot!!!
    for (uint32_t i = 0; i < function->upvalueCount(); ++i)
    {
        this->emitByte(environment.upvalues()[i].isLocal ? 1 : 0);
        this->emitByte(environment.upvalues()[i].index);
    }    
}

//! \brief compile a method
void Compiler::compileMethod()
{
    this->consumeToken(TokenType::FUN, "Expect 'fun' before method name.");
    this->consumeToken(TokenType::IDENTIFIER, "Expect method name.");
    // define the method name
    uint8_t constant = this->makeIdentifierConstant(this->_previousToken);
    
    // compile the method body
    FunctionType functionType = FunctionType::METHOD;

    // check initialzer!
    if (this->_previousToken.length == ObjectClass::initMethodName->str().size() &&
        memcmp(const_cast<char*>(this->_previousToken.start), ObjectClass::initMethodName->cstr(), this->_previousToken.length) == 0)
        functionType = FunctionType::INITIALIZER;

    this->compileFunction(functionType);
    
    this->emitBytecodeByte(OpCode::METHOD, constant); 
}

//! \brief compile a static field
void Compiler::compileField()
{
    this->consumeToken(TokenType::VAR, "Expect 'var' before static field.");
    this->consumeToken(TokenType::IDENTIFIER, "Expect field name.");
    // define the method name
    uint8_t constant = this->makeIdentifierConstant(this->_previousToken);

    this->consumeToken(TokenType::EQUAL, "Expect a assign for static filed");
    this->compileExpression();
    this->consumeToken(TokenType::SEMICOLON, "Expect ';' after experssion.");

    this->emitBytecodeByte(OpCode::FIELD, constant);
}

//! \brief parse a varibale name
//! \note register a global name, return it's name string object in constant vector' index
//! \note register a local name, return 0
uint8_t Compiler::registerVariableName(const char* errorMessage)
{
    // the token should be a identifier
    this->consumeToken(TokenType::IDENTIFIER, errorMessage);
    
    this->declareVariable();

    // a local variable, return 0!
    if (this->_currentEnvironment->scopeDepth() > 0)
        return 0;
    
    return this->makeIdentifierConstant(this->_previousToken);
}

//! \brief synchronize
void Compiler::synchronize()
{
    // the panic mode is off, but the hadError is true, so compiler will continue to compile
    // the source for finding more error for user
    // but won't execute!
    this->_panicMode = false;

    while (this->_currentToken.type != TokenType::ENDOFFILE)
    {
        // if the delaration over, return from synchronize, continue compile
        if (this->_previousToken.type == TokenType::SEMICOLON) 
            return;
        switch (this->_previousToken.type)
        {
        // meet these token suggest the next delaration start!
        case TokenType::CLASS:
        case TokenType::FUN:
        case TokenType::VAR:
        case TokenType::FOR:
        case TokenType::IF:
        case TokenType::WHILE:
        case TokenType::RETURN:
            return;
        default:;
        }

        this->advanceToken();
    }
}

//! \brief save the previous toke, and scann the next token
void Compiler::advanceToken()
{
    this->_previousToken = this->_currentToken;

    for (;;)
    {
        this->_currentToken = scanner->scanToken();
        if (this->_currentToken.type != TokenType::ERROR)
            break;

        this->errorAtCurrentToken(this->_currentToken.start);
    }
}

//! \brief check wheather current token's type is the specify one
//! \param tokenType the specify token type
//! \param message error message
void Compiler::consumeToken(TokenType tokenType, const char* message)
{
    if (this->_currentToken.type == tokenType)
    {
        this->advanceToken();
        return;
    }

    this->errorAtCurrentToken(message);
}

//! \brief check wheather current token is the specify type, if so, consumeToken it
bool Compiler::matchTokenType(TokenType tokenType)
{
    if (!this->checkTokenType(tokenType))
        return false;
    this->advanceToken();
    return true;
}

//! \brief write a jump instruction bytecode
//! \param[in] instruction
//! \return uint32_t
uint32_t Compiler::emitJump(OpCode instruction)
{
    this->emitBytecode(instruction);
    this->emitByte(0xffu);
    this->emitByte(0xffu);
    return this->currentChunk()->size() - 2;
}

//! \brief report the error
//! \param[in] token the token which happen error
//! \param[in] message the attch erroe message
void Compiler::errorAt(const Token& token, const char* message)
{
    if (this->_panicMode)
        return;
    this->_panicMode = true;

    // ouput message
    std::cout << "[line " << token.line << "] Error";
    if (token.type == TokenType::ENDOFFILE)
        std::cout << " at end: ";
    else if (token.type == TokenType::ERROR)
        {}
    else
        std::cout << " at '" << std::string(token.start, token.length) << "': ";

    std::cout << message << '\n';

    this->_hadError = true;
}

//! \brief end a compiler
ObjectFunction* Compiler::endCompiler()
{
    this->emitReturn();    

    ObjectFunction* function = this->currentFunction();

    if (vm->showBytecodes())
    {
        if (!this->_hadError)
            Disassembler::disassembleChunk(*this->currentChunk(), 
                                        function->name() != NULL ? function->name()->cstr() : "<script>");
    }

    this->_currentEnvironment = this->_currentEnvironment->enclosing();
    return function;
}

//! \brief init the new environment
void Compiler::initEnvironment(Environment* environment)
{
    environment->setEnclosing(this->_currentEnvironment);
    this->_currentEnvironment = environment;

    if (this->_currentEnvironment->functionType() != FunctionType::SCRIPT) 
    {
        this->currentFunction()->setName(allocator->allocateString(
            this->_previousToken.start, this->_previousToken.length
        ));
    }

    // if the function type is method, add "this" variable as first local variable!
    if (this->_currentEnvironment->functionType() != FunctionType::FUNCTION)
        this->_currentEnvironment->addLocal(Local(Token(TokenType::NIL, "this", 4, 0), 0));
    else
        this->_currentEnvironment->addLocal(Local(Token(TokenType::NIL, "", 0, 0), 0));
}

//! \brief create a constant in chunk's constant vector
uint8_t Compiler::makeConstant(const Value& value)
{
    uint32_t constantIndex = this->currentChunk()->addConstant(value);
    if (constantIndex > UINT8_MAX)
    {
        this->errorAtPreviousToken("Too many constants in one chunk.");
        return 0;
    }
    return constantIndex;
}

//! \brief create a object string which contains the identifier name
uint8_t Compiler::makeIdentifierConstant(const Token& name)
{
    ObjectString* string = allocator->allocateString(name.start, name.length);
    return this->makeConstant(Value(string)); 
}

//! \brief patch the jump instruction's ip address
//! \param[in] offset the jump addr index in code' vector
void Compiler::patchJump(uint32_t offset)
{
    // jumpDistance = current index - jump addr index - 2
    uint32_t jumpDistance = this->currentChunk()->size() - offset - 2;

    if (jumpDistance > UINT16_MAX)
        this->errorAtPreviousToken("Too much code to jump over.");

    this->currentChunk()->setByte(offset, (jumpDistance >> 8) & 0xff);
    this->currentChunk()->setByte(offset + 1, (jumpDistance & 0xff));
}

//! \brief parse a argument list, and return argument count
uint8_t Compiler::compileArgumentList()
{
    uint8_t argCount = 0;
    // if not a ')', the cuntion has arguments
    if (!this->checkTokenType(TokenType::RIGHT_PAREN))
    {
        do
        {
            this->compileExpression();
            if (argCount == 255)
                this->errorAtPreviousToken("Can't have more than 255 arguments.");
            argCount++;
        // see argument as some experssion, until next token is not a ','
        } while (this->matchTokenType(TokenType::COMMA));
    }

    this->consumeToken(TokenType::RIGHT_PAREN, "Expect ')' after arguments.");
    return argCount;
}

//! \brief write a return bytecode to chunk
void Compiler::emitReturn()
{ 
    if (this->_currentEnvironment->functionType() == FunctionType::INITIALIZER)
        this->emitBytecodeByte(OpCode::GET_LOCAL, 0);
    else
        this->emitBytecode(OpCode::NIL);
    this->emitBytecode(OpCode::RETURN);
}

//! \brief emit a loop instruction, and fill the jump address
void Compiler::emitLoop(uint32_t loopStart)
{
    this->emitBytecode(OpCode::LOOP);

    uint32_t offset = (this->currentChunk()->size() + 2) - loopStart;
    if (offset > UINT16_MAX)
        this->errorAtPreviousToken("Loop body too large.");
    
    this->emitByte((offset >> 8) & 0xff);
    this->emitByte(offset & 0xff);
}

//! \brief begin a scope
void Compiler::beginScope()
{
    this->_currentEnvironment->beginScope();
}

//! \brief end a scope
void Compiler::endScope()
{
    while (this->_currentEnvironment->localCount() > 0 && 
           this->_currentEnvironment->lastLocal().depth > this->_currentEnvironment->scopeDepth())
    {
        // if a local variable is been captured, the variable should not been pop
        if (this->_currentEnvironment->locals().back().isCaptured)
            this->emitBytecode(OpCode::CLOSE_UPVALUE);
        else
            this->emitBytecode(OpCode::POP);

        this->_currentEnvironment->popLastLocal();
    }

    this->_currentEnvironment->endScope();
}

//! \brief declare a varibale
//! \brief is it's a global variable, do nothing. if it's a local variable, will check the 
//! \brief variable's name(_previourToken), and add it to Environment's local vector
void Compiler::declareVariable()
{
    // global variable not need to declare
    if (this->_currentEnvironment->scopeDepth() == 0)
        return;
    
    Token name = this->_previousToken;

    // check name conflict
    for (auto iter = this->_currentEnvironment->locals().rbegin(); iter != this->_currentEnvironment->locals().rend(); ++iter)
    {
        // the variable name can be the same with the higher scope
        // this->_currentEnvironment->scopeDepth() is the current variable's scope depth
        if (iter->depth != -1 && iter->depth < this->_currentEnvironment->scopeDepth())
            break;
        
        // in one local socpe, the varibale name can'e be equal!
        if (Token::identifiersEqual(name, iter->name))
            this->errorAtPreviousToken("Already a variable with this name in this scope.");
    }

    // not name conflict, add the name into _currentEnvironment's vector
    if (this->_currentEnvironment->addLocal(name) == false)
        this->errorAtPreviousToken("Too many local variables in function.");
}

//! \brief define a varibale(if a local varable, no 'nothing'. if a global variable, emit a DEFINE_GLOBAL bytecode)
//! \brief to define it in global table
//! \param[in] global if is a global variable, 'global' store the Value in constant vector's index
void Compiler::defineVariable(uint8_t global) 
{
    // define a local variable
    if (this->_currentEnvironment->scopeDepth() > 0)
    {
        // set the scope depth of local to environment depth
        this->_currentEnvironment->markInitialized();
        return;
    }

    // emit a 'DEFINE_GLOBAL' to define a global variable
    this->emitBytecodeByte(OpCode::DEFINE_GLOBAL, global); 
}

//! \brief compile a named varibale, check is a global, local or upvalue, check it's get or set value
//! \param[in] name the variable name token
void Compiler::compileNamedVariable(const Token& name, bool canAssign)
{
    OpCode getOp;
    OpCode setOp;

    // find the name in current environment
    int arg = this->_currentEnvironment->resolveLocal(name);
    if (arg == Environment::RESOLVE_ERROR)
        this->errorAtPreviousToken("Can't read local variable in its own initializer.");

    // if find, is a local variable
    if (arg != -1)
    {
        getOp = OpCode::GET_LOCAL;
        setOp = OpCode::SET_LOCAL;
    }

    // not a local varibale, try resolve it as upvalue
    else if ((arg = this->_currentEnvironment->resolveUpvalue(name)) != -1)
    {
        if (arg == Environment::RESOLVE_ERROR)
            this->errorAtPreviousToken("Too many closure variables in function.");

        getOp = OpCode::GET_UPVALUE;
        setOp = OpCode::SET_UPVALUE;
    }

    // must be a global variable
    else
    {
        arg = this->makeIdentifierConstant(name);
        getOp = OpCode::GET_GLOBAL;
        setOp = OpCode::SET_GLOBAL;
    }

    // if there is a '=' after variable -> it a set statement
    if (canAssign && this->matchTokenType(TokenType::EQUAL))
    {
        // compile the new value
        this->compileExpression();
        this->emitBytecodeByte(setOp, arg);
    }
    // ot it just need to get it value
    else 
    {
        this->emitBytecodeByte(getOp, arg);   
    }
}

//! \brief compile a list
uint16_t Compiler::compileList()
{
    uint16_t elementSize = 0;
    
    if (!this->checkTokenType(TokenType::RIGHT_BRACKET))
    {
        do
        {
            this->compileExpression();
            if (elementSize == 0xffff)
                this->errorAtPreviousToken("Can't have more than 65536 elements.");
            elementSize += 1;
        } while (this->matchTokenType(TokenType::COMMA));
    }

    this->consumeToken(TokenType::RIGHT_BRACKET, "Expect ']' after list.");
    return elementSize;
}

//! \brief compile a dict or a set
//! \param[out] code out para, OpCode::DICT or OpCode::SET
//! \return uint16_t element size
uint16_t Compiler::compileDictOrSet(OpCode& code)
{
    uint16_t elementSize = 0;
    
    if (!this->checkTokenType(TokenType::RIGHT_BRACE))
    {
        // judge DICT or SET by first element!
        this->compileExpression();
        if (this->matchTokenType(TokenType::COLON))
        {
            // dict!
            code = OpCode::DICT;
            this->compileExpression();
            elementSize += 1;
            
            while (this->matchTokenType(TokenType::COMMA))
            {
                this->compileExpression();
                this->consumeToken(TokenType::COLON, "Expect ':' between key and value.");
                this->compileExpression();
                if (elementSize == 0xffff)
                    this->errorAtPreviousToken("Can't have more than 65536 key-value pairs.");
                elementSize += 1;
            }
        }
        else
        {
            // set
            code = OpCode::SET;
            elementSize += 1;

            while (this->matchTokenType(TokenType::COMMA))
            {
                this->compileExpression();
                if (elementSize == 0xffff)
                    this->errorAtPreviousToken("Can't have more than 65536 key-value pairs.");
                elementSize += 1;
            }
        }
    }
    else
    {
        // empty '{}', default as a dict
        code = OpCode::DICT;
    }

    this->consumeToken(TokenType::RIGHT_BRACE, "Expect '}' after dict or set.");
    return elementSize;
}

}